#pragma once


#include<csetjmp>
#include<memory>
#include<functional>

typedef size_t blength_t;
typedef  void* address_t;

#define USING_C3_LONG_JMP 1

/////////////////////////////////////////////////////////////////////////

#define CONTEXT_OFFSET_OF(membr)    ((blength_t)(void*)&(((context*)nullptr)->##membr))

#ifdef __cplusplus
#define CHECK_OFFSET(membr,aim)   static_assert(CONTEXT_OFFSET_OF(membr)==(aim)*sizeof(blength_t),"NOT MATCH!")  
#else
#define CHECK_OFFSET(membr,aim)
#endif


typedef blength_t c3_fast_context[10];


#if USING_C3_LONG_JMP

#define set_jmp c3_set_jmp
#define long_jmp c3_long_jmp
#define c3_fast_context c3_fast_context

#else

#define c3_fast_context std::jmp_buf
#define set_jmp(buf) std::setjmp(buf)
#define long_jmp(buf,val) std::longjmp(buf, val)


#endif

struct context
{
    // be called function information.
    constexpr static blength_t C_STACK_MAX_SIZE = 512;
    address_t c_ip = 0;
    blength_t c_regs[8] = { 0 };
    address_t c_bp = 0;
    blength_t c_stack_sz = 0;
    blength_t c_stack[C_STACK_MAX_SIZE] = {};
    ////////////////////////////////////////////////

    // invoke inform (used for fast go back)
    blength_t           i_destroy = 0;
    c3_fast_context i_buf;
};

CHECK_OFFSET(c_ip, 0x00);
CHECK_OFFSET(c_regs, 0x01);
CHECK_OFFSET(c_bp, 0x09);
CHECK_OFFSET(c_stack_sz, 0x0a);
CHECK_OFFSET(c_stack, 0x0b);
// CHECK_OFFSET(i_buf, 0x0b + c3_context::C_STACK_MAX_SIZE);        // i_buf not used in asm, so no need to check.

typedef context* volatile c3_context;

    extern "C" void c3_jmp_context(c3_context j_c3_context);
    extern "C" void c3_restore_context(c3_context j_c3_context);
    extern "C" void c3_save_context(c3_context j_c3_context);
    extern "C" address_t c3_get_call_bp(...);
    extern "C" blength_t c3_set_jmp(c3_fast_context j_c3_fast_context);
    extern "C" void c3_long_jmp(c3_fast_context j_c3_fast_context, blength_t val);

namespace c3
{

    //template<typename ... T>
    //bool invoke_coroutine(c3_context write_context, address_t aim_function_address, T... args)
    //{
    //    write_context->c_bp = c3_get_call_bp(write_context, args...);
    //    if (!set_jmp(write_context->i_buf))
    //        ((void(*)(c3_context, T...))aim_function_address)(write_context, args...);

    //    return (bool)write_context->c_ip;
    //}

    template<typename F, typename ... T>
    bool invoke_coroutine(c3_context write_context, F aim_function_address, T... args)
    {
        write_context->c_bp = c3_get_call_bp(write_context, args...);
        if (!set_jmp(write_context->i_buf))
            (aim_function_address)(write_context, args...);

        return (bool)write_context->c_ip;
    }

 #if 0
    template<typename R>
    struct coroutine_promise
    {
        bool not_end = false;
        R result;

        operator bool()const
        {
            return not_end;
        }

        R get() const
        {
            return R;
        }
    };

    template<typename T>
    struct coroutine_promise<T&>
    {
        bool not_end = false;
        T* result;

        operator bool()const
        {
            return not_end;
        }

        T& get() const
        {
            return *result;
        }
    };
#endif

    template<typename FTYPE>
    class coroutine_function
    {
        mutable std::shared_ptr<context> coroutine_context=std::make_shared<context>();
        std::function<FTYPE> coroutine_func;

        mutable std::function<bool(void)>this_bind_pack;
    public:
        coroutine_function(const coroutine_function&) = default;
        coroutine_function(coroutine_function&&) = default;



        coroutine_function(std::function<FTYPE> func) :
            coroutine_func(func)
        {

        }
        coroutine_function(FTYPE func) :
            coroutine_func(func)
        {

        }
        ~coroutine_function()
        {
            if (coroutine_context->c_ip && this_bind_pack)
            {
                coroutine_context->i_destroy = 1;
                this_bind_pack();
            }
        }
        template<typename ... ARGT>
        bool operator ()(ARGT...args)const
        {
            if (!this_bind_pack)
            {
                this_bind_pack = [=]()
                {
                    return invoke_coroutine(coroutine_context.get(), coroutine_func, args...);
                };
            }
            return this_bind_pack();
        }
    };

    template<typename FTYPE>
    using co_function = coroutine_function<FTYPE>;
}

#define c3_context c3_context
#define c3_restore(j_context) c3_jmp_context(j_context)
#define c3_yield(j_context)  c3_save_context(j_context); if (j_context->c_ip)long_jmp(j_context->i_buf, 1);if(j_context->i_destroy)return; c3_restore_context(j_context)

#undef CONTEXT_OFFSET_OF
#undef CHECK_OFFSET